#include "config.h"

#include <errno.h>
#include <stdint.h>

#define DBG_SUBSYS S_LIBYLIB

#include "sysutil.h"
#include "sysy_lib.h"
#include "timer.h"
#include "dbg.h"
#include "bh.h"

typedef struct {
        struct list_head hook;
        char name[MAX_NAME_LEN];
        func_t exec;
        void *arg;
        int step;
        uint64_t tmo;
} entry_t;

typedef struct {
        struct list_head list;
        struct list_head add_list;
        sy_spinlock_t lock;
} bh_t;

static bh_t bh;
static int inited = 0;
static worker_handler_t bh_handler;

static int __exec(entry_t *ent, uint64_t now)
{
        int used = 0;
        struct timeval t1, t2;

        if (ent->tmo < now) {
                _gettimeofday(&t1, NULL);

                ent->exec(ent->arg);

                _gettimeofday(&t2, NULL);
                used = _time_used(&t1, &t2);

                if (used > 100 * 1000) {
                        DINFO("bh %s used %u ms\n", ent->name, 100);
                }

                ent->tmo = ytime_gettime() + ent->step * 1000 * 1000;
        }

        return used;
}

static int __bh_worker(void *_args)
{
        int ret;
        entry_t *ent;
        struct list_head *pos, *n;
        uint64_t now;

        (void) _args;

        now = ytime_gettime();

        ret = sy_spin_lock(&bh.lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (!list_empty(&bh.add_list)) {
                list_splice_init(&bh.add_list, &bh.list);
        }

        sy_spin_unlock(&bh.lock);

        list_for_each_safe(pos, n, &bh.list) {
                ent = (void *)pos;

                DINFO("%s step %u now %u tmo %u\n", ent->name,
                     ent->step, (int)now, (int)ent->tmo);

                __exec(ent, now);
        }

        ret = timer1_settime(&bh_handler, USEC_PER_SEC);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int bh_init()
{
        int ret;

        YASSERT(inited == 0);

        INIT_LIST_HEAD(&bh.list);
        INIT_LIST_HEAD(&bh.add_list);

        ret = sy_spin_init(&bh.lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = timer1_create(&bh_handler, "bh", __bh_worker, NULL);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = timer1_settime(&bh_handler, USEC_PER_SEC);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        inited = 1;

        return 0;
err_ret:
        return ret;
}

int bh_register(const char *name,  func_t func, void *args, int sec)
{
        int ret;
        entry_t *ent;

        YASSERT(inited);

        ret = ymalloc((void *)&ent, sizeof(entry_t));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        strcpy(ent->name, name);
        ent->exec = func;
        ent->arg = args;
        ent->step = sec;
        ent->tmo = ytime_gettime() + sec * 1000 * 1000;

        ret = sy_spin_lock(&bh.lock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        list_add_tail(&ent->hook, &bh.add_list);

        sy_spin_unlock(&bh.lock);

        return 0;
err_ret:
        return ret;
}
